﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Diagnostics;
using Engine.Dtd.Schema;

namespace Engine.Sgml {
    /// <summary>
    /// Represents an element or an attribute in an SGML tree.
    /// </summary>
    //[DebuggerStepThrough]
    public abstract class SObject : IEquatable<SObject> {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        internal static StringComparison NameComparisonType = StringComparison.InvariantCultureIgnoreCase;


        /// <summary>
        /// Raised when this SObject or any of its descendants are about to change.
        /// </summary>
        [field: DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public event EventHandler<SObjectChangeEventArgs> Changing;

        /// <summary>
        /// Raises the Changing event.
        /// </summary>
        /// <param name="sender">A changing SElement object.</param>
        /// <param name="e">A SObjectChangeEventArgs event argument.</param>
        protected void OnChanging(SObject sender, SObjectChangeEventArgs e) {
            Debug.Assert(e != null);
            Debug.Assert(sender != null);
            if (Changing != null)
                Changing(sender, e);
            if (parent != null)
                parent.OnChanging(sender, e);
            }


        /// <summary>
        /// Raised when this SObject or any of its descendants have changed.
        /// </summary>
        [field: DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public event EventHandler<SObjectChangeEventArgs> Changed;

        /// <summary>
        /// Raises the Changed event.
        /// </summary>
        /// <param name="sender">A changed SElement objcet.</param>
        /// <param name="e">A SObjectChangeEventArgs evetn argument.</param>
        protected void OnChanged(SObject sender, SObjectChangeEventArgs e) {
            Debug.Assert(e != null);
            Debug.Assert(sender != null);
            if (Changed != null)
                Changed(sender, e);
            if (parent != null)
                parent.OnChanged(sender, e);
            }

        /// <summary>
        /// Gets the local part of the name.
        /// </summary>
        public string LocalName {
            get { return sname.LocalName; }
            }

        /// <summary>
        /// Gets the namespace part of the fully qualified name.
        /// </summary>
        public string Prefix {
            get { return sname.Prefix; }
            }

        /// <summary>
        /// Gets the node type for this node.
        /// </summary>
        public SgmlNodeType NodeType { get; internal set; }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string value;
        /// <summary>
        /// Gets or sets the text contents of this object.
        /// </summary>
        public string Value {
            get {
                if (value == null)
                    return SName.EmptyString;
                else
                    return value;
                }
            set { this.value = value; }
            }

        /// <summary>
        /// Resets the members of this class to their default values.
        /// </summary>
        internal virtual void Clear() {
            sname = SName.Empty;
            value = null;
            parent = null;
            DName = null;
            NodeType = SgmlNodeType.None;
            }

        /// <summary>
        /// Copies information from specified element.
        /// </summary>
        /// <param name="element">An element from which to copy information.</param>
        protected void CopyFrom(SObject element) {
            Guard.ArgumentNotNull(element, "element");
            sname = element.sname;
            DName = element.DName;
            NodeType = element.NodeType;
            value = element.Value;
            }

        /// <summary>
        /// Returns a value indicating whether two instances of SObject are equal.
        /// </summary>
        /// <param name="first">The first SObject to compare.</param>
        /// <param name="second">The second SObject to compare.</param>
        /// <returns>true if the <paramref name="first"/> and <paramref name="second"/> parameters represent the same SObject object, otherwise, false.</returns>
        public static bool operator ==(SObject first, SObject second) {
            if (object.ReferenceEquals(first, second))
                return true;
            if (object.ReferenceEquals(first, null) || object.ReferenceEquals(second, null))
                return false;
            else
                return first.sname == second.sname &&
                        object.ReferenceEquals(first.parent, second.parent) &&
                        first.orderIndex == second.orderIndex;
            }

        /// <summary>
        /// Returns a value indicating whether two instances of SObject are not equal.
        /// </summary>
        /// <param name="first">The first SObject to compare.</param>
        /// <param name="second">The second SObject to compare.</param>
        /// <returns>true if the <paramref name="first"/> and <paramref name="second"/> parameters represent different SObject objects, otherwise, false.</returns>
        public static bool operator !=(SObject first, SObject second) {
            return !(first == second);
            }

        /// <summary>
        /// Determines whether this SGML object and the specified SObject are equal.
        /// </summary>
        /// <param name="other">A SObject to compare to this instance.</param>
        /// <returns>true if <paramref name="other"/> is equal to the current SObject instance; otherwise, false.</returns>
        public bool Equals(SObject other) {
            if (object.ReferenceEquals(other, null))
                return false;
            return this == other;
            }

        internal DName DName { get; set; }

        /// <summary>
        /// Determines whether the name of this SGML object is equal to the specified string.
        /// </summary>
        /// <param name="other">A string that contains the name of an SGML object.</param>
        /// <returns>true if <paramref name="other"/> is equal to the fully qualified name of current SObject; otherwise, false.</returns>
        internal bool Equals(string other) {
            if (string.IsNullOrEmpty(other))
                return false;
            if (string.IsNullOrEmpty(Prefix))
                return string.Equals(LocalName, other, NameComparisonType);
            else
                return string.Equals(Name.ToString(), other, NameComparisonType);
            }

        /// <summary>
        /// Determines whether this SGML object and the specified object are equal.
        /// </summary>
        /// <param name="obj">A object to compare to this instance.</param>
        /// <returns>true if <paramref name="obj"/> is a SObject and equal to the current SName instance; otherwise, false.</returns>
        public override bool Equals(object obj) {
            return Equals(obj as SObject);
            }

        /// <summary>
        /// Returns the hash code for this SObject.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode() {
            if (object.ReferenceEquals(sname, null))
                return LocalName.GetHashCode();
            else
                return sname.GetHashCode();
            }

        /// <summary>
        /// Returns a string that represents the current SObject.
        /// </summary>
        /// <returns>A string that represents this instance.</returns>
        public override string ToString() {
            switch (NodeType) {
                case SgmlNodeType.CDataSection:
                    SgmlTextFormatWriter writer = new SgmlTextFormatWriter();
                    writer.WriteDataSection(Name, Value);
                    return writer.ToString();
                case SgmlNodeType.Comment:
                    writer = new SgmlTextFormatWriter();
                    writer.WriteComment(Value);
                    return writer.ToString();
                case SgmlNodeType.Document:
                    return LocalName;
                case SgmlNodeType.DocumentType:
                    writer = new SgmlTextFormatWriter();
                    writer.WriteDoctype((SElement)this);
                    return writer.ToString();
                case SgmlNodeType.Element:
                    writer = new SgmlTextFormatWriter();
                    writer.WriteStartElement((SElement)this);
                    return writer.ToString();
                case SgmlNodeType.EndElement:
                    writer = new SgmlTextFormatWriter();
                    writer.WriteEndElement(Name);
                    return writer.ToString();
                case SgmlNodeType.Text:
                case SgmlNodeType.CData:
                    return Value;
                case SgmlNodeType.WhiteSpace:
                case SgmlNodeType.SignificantWhiteSpace:
                    return Value;
                case SgmlNodeType.Condition:
                    writer = new SgmlTextFormatWriter();
                    writer.WriteConditional(LocalName, Value);
                    return writer.ToString();
                case SgmlNodeType.ProcessingInstruction:
                    writer = new SgmlTextFormatWriter();
                    writer.WriteProcessingInstruction((SElement)this);
                    return writer.ToString();
                default:
                    return Name.ToString();
                }
            }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private SName sname;
        /// <summary>
        /// Gets the name of this element.
        /// </summary>
        public SName Name {
            get { return sname; }
            internal set { sname = value; }
            }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private SElement parent;
        /// <summary>
        /// Gets the parent element of this object.
        /// </summary>
        public SElement Parent {
            get { return parent; }
            internal set { parent = value; }
            }

        /// <summary>
        /// Gets the string that contains the contextual information about the destination
        /// of this element.
        /// </summary>
        /// <returns>A string containing the contextual information.</returns>
        internal string Context() {
            return string.Format("Ln {0} Col {1}", LineNumber, LinePosition);
            }

        /// <summary>
        /// Gets or sets the line number where this object is found.
        /// </summary>
        public int LineNumber { get; set; }

        /// <summary>
        /// Gets or sets the line position where this object is found.
        /// </summary>
        public int LinePosition { get; set; }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private int orderIndex;
        /// <summary>
        /// Gets or sets the order index in which this SObject is located
        /// in a childs element collection.
        /// </summary>
        internal int OrderIndex {
            get { return orderIndex; }
            set { orderIndex = value; }
            }
        }
    }
